import serial
import serial.tools.list_ports
import time
import threading
import webbrowser
import math
from flask import Flask, render_template_string, jsonify

app = Flask(__name__)

class BN808Tracker:
    def __init__(self):
        self.lat = 30.5224
        self.lon = 114.3578
        self.original_lat = 30.5224
        self.original_lon = 114.3578
        self.speed_kmh = 0.0
        self.fix_status = 0
        self.fix_mode = 1  # 1=无定位, 2=2D, 3=3D
        self.satellites = 0
        self.pdop = 99.9
        self.hdop = 99.9
        self.vdop = 99.9
        self.track = []  # 存储轨迹点，每个点包含 [lon, lat, speed]
        self.max_track_points = 10000
        self.ser = None
        self.running = True
        self.data_lock = threading.Lock()
        self.raw_data = "等待数据..."
        self.data_count = 0
        self.last_nmea_type = ""
        self.connection_info = "未连接"
        self.utc_time = ""

    def wgs84_to_gcj02(self, lon, lat):
        """
        WGS84转GCJ02(火星坐标系)
        参考: https://github.com/wandergis/coordTransform
        """
        a = 6378245.0  # 长半轴
        ee = 0.00669342162296594323  # 扁率
        
        # 判断是否在国内
        if (lon < 72.004 or lon > 137.8347 or 
            lat < 0.8293 or lat > 55.8271):
            return lon, lat
        
        dlat = self._transform_lat(lon - 105.0, lat - 35.0)
        dlon = self._transform_lon(lon - 105.0, lat - 35.0)
        
        radlat = lat / 180.0 * math.pi
        magic = math.sin(radlat)
        magic = 1 - ee * magic * magic
        sqrtmagic = math.sqrt(magic)
        
        dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * math.pi)
        dlon = (dlon * 180.0) / (a / sqrtmagic * math.cos(radlat) * math.pi)
        
        mglat = lat + dlat
        mglon = lon + dlon
        
        return mglon, mglat

    def _transform_lat(self, x, y):
        ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * math.sqrt(abs(x))
        ret += (20.0 * math.sin(6.0 * x * math.pi) + 20.0 * math.sin(2.0 * x * math.pi)) * 2.0 / 3.0
        ret += (20.0 * math.sin(y * math.pi) + 40.0 * math.sin(y / 3.0 * math.pi)) * 2.0 / 3.0
        ret += (160.0 * math.sin(y / 12.0 * math.pi) + 320 * math.sin(y * math.pi / 30.0)) * 2.0 / 3.0
        return ret

    def _transform_lon(self, x, y):
        ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * math.sqrt(abs(x))
        ret += (20.0 * math.sin(6.0 * x * math.pi) + 20.0 * math.sin(2.0 * x * math.pi)) * 2.0 / 3.0
        ret += (20.0 * math.sin(x * math.pi) + 40.0 * math.sin(x / 3.0 * math.pi)) * 2.0 / 3.0
        ret += (150.0 * math.sin(x / 12.0 * math.pi) + 300.0 * math.sin(x / 30.0 * math.pi)) * 2.0 / 3.0
        return ret

    def get_map_html(self):
        return """
        <!DOCTYPE html>
        <html>
        <head>
            <title>BN808 GPS追踪</title>
            <meta charset="UTF-8">
            <script src="https://webapi.amap.com/maps?v=2.0&key=b06fe82775b0d426b648a2385dca9c94"></script>
            <style>
                body {margin:0; padding:0; font-family:Arial;}
                #mapContainer {width:100%; height:100vh;}
                .info-panel {
                    position: absolute;
                    top: 10px;
                    left: 10px;
                    background: rgba(255,255,255,0.95);
                    padding: 15px;
                    border-radius: 8px;
                    box-shadow: 0 2px 10px rgba(0,0,0,0.3);
                    min-width: 300px;
                    z-index: 1000;
                }
                .status-item {margin: 8px 0; font-size: 14px;}
                .status-label {display:inline-block; width: 120px;}
                .status-value {font-weight: bold;}
                .no-fix {color: #ff4d4f;}
                .has-fix {color: #52c41a;}
                .raw-data {
                    position: absolute;
                    bottom: 10px;
                    left: 10px;
                    right: 10px;
                    background: rgba(0,0,0,0.8);
                    color: white;
                    padding: 10px;
                    border-radius: 5px;
                    font-family: monospace;
                    font-size: 12px;
                    max-height: 100px;
                    overflow-y: auto;
                }
                .speed-legend {
                    position: absolute;
                    bottom: 120px;
                    left: 10px;
                    background: rgba(255,255,255,0.9);
                    padding: 10px;
                    border-radius: 5px;
                    box-shadow: 0 2px 5px rgba(0,0,0,0.2);
                    font-size: 12px;
                }
                .legend-item {
                    display: flex;
                    align-items: center;
                    margin: 5px 0;
                }
                .legend-color {
                    width: 20px;
                    height: 4px;
                    margin-right: 8px;
                }
            </style>
        </head>
        <body>
            <div id="mapContainer"></div>
            <div class="info-panel">
                <h3 style="margin-top:0;">BN808 GPS追踪器</h3>
                <div class="status-item">
                    <span class="status-label">连接状态:</span>
                    <span id="connectionStatus" class="status-value no-fix">未连接</span>
                </div>
                <div class="status-item">
                    <span class="status-label">实时速度:</span>
                    <span id="speedValue" class="status-value">0.0</span> km/h
                </div>
            </div>
            <div class="speed-legend">
                <div style="font-weight: bold; margin-bottom: 5px;">速度颜色图例</div>
                <div class="legend-item">
                    <div class="legend-color" style="background: #00ff00;"></div>
                    <span>0 km/h</span>
                </div>
                <div class="legend-item">
                    <div class="legend-color" style="background: #ffff00;"></div>
                    <span>10 km/h</span>
                </div>
                <div class="legend-item">
                    <div class="legend-color" style="background: #ff0000;"></div>
                    <span>20+ km/h</span>
                </div>
            </div>
            <div class="raw-data">
                <strong>原始数据:</strong> <span id="rawData">等待数据...</span>
            </div>

            <script>
                // 初始化地图
                var map = new AMap.Map('mapContainer', {
                    zoom: 16,
                    center: [114.3578, 30.5224],
                    resizeEnable: true
                });

                // 创建标记点
                var marker = new AMap.Marker({
                    position: [114.3578, 30.5224],
                    icon: 'https://webapi.amap.com/theme/v1.3/markers/n/mark_b.png',
                    offset: new AMap.Pixel(-13, -30)
                });
                map.add(marker);

                // 创建轨迹线数组，每条线段根据速度有不同的颜色
                var polylines = [];

                // 根据速度获取颜色 (速度越快越红，越慢越绿)
                function getColorBySpeed(speed) {
                    // 限制速度范围在0-60 km/h
                    var normalizedSpeed = Math.min(speed,20) / 20;
                    
                    // 计算RGB值
                    var r = Math.floor(255 * normalizedSpeed);
                    var g = Math.floor(255 * (1 - normalizedSpeed));
                    var b = 0;
                    
                    return "rgb(" + r + "," + g + "," + b + ")";
                }

                // 更新轨迹显示
                function updateTrack(trackData) {
                    // 清除所有现有轨迹线
                    polylines.forEach(function(polyline) {
                        map.remove(polyline);
                    });
                    polylines = [];
                    
                    if (trackData.length < 2) return;
                    
                    // 创建新的轨迹线，每段根据速度设置颜色
                    for (var i = 0; i < trackData.length - 1; i++) {
                        var point1 = [trackData[i][0], trackData[i][1]];
                        var point2 = [trackData[i+1][0], trackData[i+1][1]];
                        var speed = trackData[i][2]; // 使用第一个点的速度作为线段速度
                        
                        var polyline = new AMap.Polyline({
                            path: [point1, point2],
                            strokeColor: getColorBySpeed(speed),
                            strokeWeight: 6,
                            strokeOpacity: 0.8,
                            lineJoin: 'round'
                        });
                        
                        map.add(polyline);
                        polylines.push(polyline);
                    }
                }

                // 更新地图数据
                function updateMapData() {
                    fetch('/data')
                        .then(response => response.json())
                        .then(data => {
                            // 更新状态信息
                            document.getElementById('connectionStatus').textContent = data.connection_info;
                            document.getElementById('connectionStatus').className = data.connected ? 
                                'status-value has-fix' : 'status-value no-fix';

                            document.getElementById('speedValue').textContent = data.speed;
                            document.getElementById('rawData').textContent = data.raw_data;

                            // 更新地图位置
                            var newPosition = [data.lon, data.lat];
                            marker.setPosition(newPosition);

                            // 如果有定位，自动居中
                            if (data.fix_status > 0) {
                                map.setCenter(newPosition);
                            }

                            // 更新轨迹
                            if (data.track.length > 0) {
                                updateTrack(data.track);
                            }
                        })
                        .catch(error => {
                            console.log('数据获取失败:', error);
                            document.getElementById('connectionStatus').textContent = '连接错误';
                            document.getElementById('connectionStatus').className = 'status-value no-fix';
                        });
                }

                // 每秒更新一次
                setInterval(updateMapData, 1000);
                updateMapData();
            </script>
        </body>
        </html>
        """

    def connect_serial(self):
        """连接串口设备"""
        print("=" * 50)
        print("BN808 GPS追踪器")
        print("=" * 50)

        # 列出所有串口
        ports = list(serial.tools.list_ports.comports())
        if not ports:
            print("错误: 未找到任何串口设备!")
            return False

        print("发现以下串口设备:")
        for i, port in enumerate(ports):
            print(f"  {i}: {port.device} - {port.description}")

        # 选择串口
        try:
            choice = int(input("请选择设备序号: "))
            port_name = ports[choice].device
            print(f"选择设备: {port_name}")
        except:
            print("选择无效，使用第一个设备")
            port_name = ports[0].device

        # 尝试不同波特率
        baudrates = [9600, 115200, 4800, 38400, 57600, 19200]

        for baud in baudrates:
            try:
                print(f"尝试连接 {port_name} 波特率 {baud}...")

                self.ser = serial.Serial(
                    port=port_name,
                    baudrate=baud,
                    timeout=1,
                    bytesize=serial.EIGHTBITS,
                    parity=serial.PARITY_NONE,
                    stopbits=serial.STOPBITS_ONE
                )

                # 清空缓冲区
                self.ser.flushInput()

                # 测试读取
                print("测试读取数据...")
                test_data = []
                start_time = time.time()
                while time.time() - start_time < 3:  # 最多等待3秒
                    if self.ser.in_waiting:
                        line = self.ser.readline().decode('utf-8', errors='ignore').strip()
                        if line:
                            test_data.append(line)
                            print(f"  收到: {line}")
                            if len(test_data) >= 5:  # 收集到5条数据就停止
                                break
                    time.sleep(0.1)

                if test_data:
                    print(f"✓ 成功连接 {port_name} 波特率 {baud}")
                    print(f"已收到 {len(test_data)} 条数据")
                    self.connection_info = f"已连接 {port_name}@{baud}"
                    return True
                else:
                    self.ser.close()
                    self.ser = None
                    print(f"  波特率 {baud} 无数据")

            except Exception as e:
                print(f"  波特率 {baud} 连接失败: {e}")
                if self.ser:
                    self.ser.close()
                    self.ser = None

        print("所有波特率连接尝试均失败!")
        return False

    def parse_coordinates(self, lat_str, lat_dir, lon_str, lon_dir):
        """解析经纬度坐标并转换为GCJ-02坐标系"""
        try:
            original_lat = self.original_lat
            original_lon = self.original_lon
            
            # 纬度: ddmm.mmmm
            if len(lat_str) >= 4:
                lat_deg = float(lat_str[:2])
                lat_min = float(lat_str[2:])
                lat = lat_deg + lat_min / 60.0
                if lat_dir == 'S':
                    lat = -lat
                original_lat = lat

            # 经度: dddmm.mmmm
            if len(lon_str) >= 5:
                lon_deg = float(lon_str[:3])
                lon_min = float(lon_str[3:])
                lon = lon_deg + lon_min / 60.0
                if lon_dir == 'W':
                    lon = -lon
                original_lon = lon
                
            # 坐标转换: WGS84 -> GCJ02
            gcj_lon, gcj_lat = self.wgs84_to_gcj02(original_lon, original_lat)
            
            self.lat = round(gcj_lat, 6)
            self.lon = round(gcj_lon, 6)
            self.original_lat = round(original_lat, 6)
            self.original_lon = round(original_lon, 6)
            
            print(f"坐标转换: WGS84({original_lat:.6f}, {original_lon:.6f}) -> GCJ02({self.lat:.6f}, {self.lon:.6f})")
                
        except Exception as e:
            print(f"坐标解析错误: {e}")

    def parse_gga(self, parts):
        """解析GGA定位数据"""
        if len(parts) < 10:
            return
            
        print(f"解析GGA: {parts}")
        
        # 定位质量 (0=无效, 1=GPS, 2=DGPS, 4=RTK, 5=浮点RTK)
        fix_quality = int(parts[6]) if parts[6] else 0
        self.fix_status = 1 if fix_quality > 0 else 0
        
        # 卫星数量
        self.satellites = int(parts[7]) if parts[7] else 0
        
        # UTC时间
        if parts[1]:
            utc_str = parts[1]
            if len(utc_str) >= 6:
                self.utc_time = f"{utc_str[:2]}:{utc_str[2:4]}:{utc_str[4:6]}"
        
        # HDOP (来自GGA)
        if parts[8]:
            try:
                self.hdop = float(parts[8])
            except (ValueError, TypeError):
                self.hdop = 99.9
        
        # 解析经纬度
        if fix_quality > 0 and parts[2] and parts[4]:
            self.parse_coordinates(parts[2], parts[3], parts[4], parts[5])
            
            # 添加轨迹点 (包含速度信息)
            with self.data_lock:
                new_point = [self.lon, self.lat, self.speed_kmh]
                self.track.append(new_point)
                if len(self.track) > self.max_track_points:
                    self.track.pop(0)
                    
            print(f"位置更新: {self.lat:.6f}, {self.lon:.6f}, 卫星: {self.satellites}, 定位质量: {fix_quality}")
        else:
            print(f"无有效定位: 定位质量={fix_quality}, 纬度={parts[2]}, 经度={parts[4]}")

    def parse_rmc(self, parts):
        """解析RMC导航数据"""
        if len(parts) < 10:
            return
            
        print(f"解析RMC: {parts}")

        if parts[2] == 'A':  # 数据有效
            print("RMC数据有效")

            # UTC时间
            if parts[1]:
                utc_str = parts[1]
                if len(utc_str) >= 6:
                    self.utc_time = f"{utc_str[:2]}:{utc_str[2:4]}:{utc_str[4:6]}"

            # 速度 (节转换为km/h)
            if parts[7]:
                speed_knots = float(parts[7])
                self.speed_kmh = round(speed_knots * 1.852, 1)
                print(f"速度更新: {self.speed_kmh} km/h")

            # 如果GGA没有定位，尝试从RMC解析
            if self.fix_status == 0 and parts[3] and parts[5]:
                print(f"从RMC解析位置: 纬度={parts[3]}{parts[4]}, 经度={parts[5]}{parts[6]}")

                self.parse_coordinates(parts[3], parts[4], parts[5], parts[6])
                self.fix_status = 1

                # 添加轨迹点 (包含速度信息)
                with self.data_lock:
                    new_point = [self.lon, self.lat, self.speed_kmh]
                    self.track.append(new_point)
                    if len(self.track) > self.max_track_points:
                        self.track.pop(0)

                print(f"位置更新: {self.lat:.6f}, {self.lon:.6f}")

    def parse_vtg(self, parts):
        """解析VTG速度数据"""
        if len(parts) < 9:
            return
            
        print(f"解析VTG: {parts}")
        if parts[7]:  # 地面速度 km/h
            self.speed_kmh = round(float(parts[7]), 1)
            print(f"VTG速度更新: {self.speed_kmh} km/h")

    def parse_gsa(self, parts):
        """解析GSA卫星状态数据"""
        if len(parts) < 18:
            print(f"GSA字段不足: 只有{len(parts)}个字段，需要至少18个")
            return
            
        print(f"解析GSA: {parts}")
        
        # 定位模式: 1=无定位, 2=2D, 3=3D
        self.fix_mode = int(parts[2]) if parts[2] else 1
        
        # 根据系统类型确定字段索引
        msg_type = parts[0]
        
        # 默认索引
        pdop_index = 15
        hdop_index = 16
        vdop_index = 17
        
        # 某些系统可能有不同的字段排列
        if msg_type in ['$GNGSA', '$BDGSA', '$GLGSA']:
            # 多系统GSA可能有不同格式
            if len(parts) > 18:
                # 可能有额外的系统标识字段
                pdop_index = 16
                hdop_index = 17
                vdop_index = 18
        
        # 确保索引不越界
        if len(parts) <= vdop_index:
            print(f"字段索引超出范围: 需要索引{vdop_index}，但只有{len(parts)}个字段")
            vdop_index = len(parts) - 1 if len(parts) > 17 else 17
        
        print(f"GSA字段详情 (使用索引 PDOP:{pdop_index}, HDOP:{hdop_index}, VDOP:{vdop_index}):")
        print(f"  PDOP: '{parts[pdop_index] if len(parts) > pdop_index else 'N/A'}'")
        print(f"  HDOP: '{parts[hdop_index] if len(parts) > hdop_index else 'N/A'}'")
        print(f"  VDOP: '{parts[vdop_index] if len(parts) > vdop_index else 'N/A'}'")
        
        # 解析DOP值
        self.parse_dop_values(parts, pdop_index, hdop_index, vdop_index)
        
        print(f"定位模式: {self.fix_mode}, PDOP: {self.pdop:.1f}, HDOP: {self.hdop:.1f}, VDOP: {self.vdop:.1f}")

    def parse_dop_values(self, parts, pdop_idx, hdop_idx, vdop_idx):
        """解析DOP值"""
        # PDOP
        if len(parts) > pdop_idx and parts[pdop_idx]:
            try:
                self.pdop = float(parts[pdop_idx])
            except (ValueError, TypeError):
                self.pdop = 99.9
                print(f"PDOP解析错误: '{parts[pdop_idx]}'")
        else:
            self.pdop = 99.9
            
        # HDOP
        if len(parts) > hdop_idx and parts[hdop_idx]:
            try:
                self.hdop = float(parts[hdop_idx])
            except (ValueError, TypeError):
                self.hdop = 99.9
                print(f"HDOP解析错误: '{parts[hdop_idx]}'")
        else:
            self.hdop = 99.9
            
        # VDOP
        if len(parts) > vdop_idx and parts[vdop_idx]:
            try:
                self.vdop = float(parts[vdop_idx])
            except (ValueError, TypeError):
                self.vdop = 99.9
                print(f"VDOP解析错误: '{parts[vdop_idx]}'")
        else:
            self.vdop = 99.9

    def parse_nmea(self, line):
        """解析NMEA数据"""
        line = line.strip()
        if not line:
            return

        self.data_count += 1
        self.raw_data = line

        # 记录最后一条NMEA消息类型
        if line.startswith('$'):
            parts = line.split(',')
            if parts:
                self.last_nmea_type = parts[0]

        print(f"收到NMEA数据: {line}")  # 调试输出

        try:
            parts = line.split(',')
            msg_type = parts[0]

            # 扩展支持的NMEA类型
            if any(msg_type.startswith(prefix) for prefix in ['$GNGGA', '$GPGGA', '$GLGGA', '$GAGGA', '$BDGGA']):
                self.parse_gga(parts)
            elif any(msg_type.startswith(prefix) for prefix in ['$GNRMC', '$GPRMC', '$GLRMC', '$GARMC', '$BDRMC']):
                self.parse_rmc(parts)
            elif any(msg_type.startswith(prefix) for prefix in ['$GNVTG', '$GPVTG', '$GLVTG', '$GAVTG', '$BDVTG']):
                self.parse_vtg(parts)
            elif any(msg_type.startswith(prefix) for prefix in ['$GNGSA', '$GPGSA', '$GLGSA', '$GAGSA', '$BDGSA']):
                self.parse_gsa(parts)
            elif any(msg_type.startswith(prefix) for prefix in ['$GPGSV', '$GNGSV', '$GLGSV', '$GAGSV', '$BDGSV']):
                # GSV语句包含卫星详细信息，可以进一步解析
                print(f"收到GSV数据: {len(parts)}个字段")
                
        except Exception as e:
            print(f"解析NMEA数据错误: {e}")

    def read_serial_data(self):
        """读取串口数据的线程函数"""
        buffer = ""
        print("开始读取GPS数据...")

        while self.running and self.ser and self.ser.is_open:
            try:
                # 读取所有可用数据
                if self.ser.in_waiting > 0:
                    data = self.ser.read(self.ser.in_waiting).decode('utf-8', errors='ignore')
                    buffer += data

                    # 处理完整行
                    while '\n' in buffer:
                        line, buffer = buffer.split('\n', 1)
                        if line.strip():
                            self.parse_nmea(line.strip())

                time.sleep(0.1)

            except Exception as e:
                print(f"读取串口数据错误: {e}")
                time.sleep(1)

    def setup_routes(self):
        """设置Flask路由"""

        @app.route('/')
        def index():
            return render_template_string(self.get_map_html())

        @app.route('/data')
        def get_data():
            with self.data_lock:
                return jsonify({
                    'lat': self.lat,
                    'lon': self.lon,
                    'original_lat': self.original_lat,
                    'original_lon': self.original_lon,
                    'speed': self.speed_kmh,
                    'fix_status': self.fix_status,
                    'raw_data': self.raw_data,
                    'track': self.track,
                    'connected': self.ser is not None and self.ser.is_open,
                    'connection_info': self.connection_info,
                })

    def start(self):
        """启动追踪器"""
        print("启动GPS追踪器...")
        
        # 连接GPS设备
        if not self.connect_serial():
            print("无法连接GPS设备，程序退出")
            return

        # 设置Web路由
        self.setup_routes()

        # 启动数据读取线程
        thread = threading.Thread(target=self.read_serial_data, daemon=True)
        thread.start()
        print("GPS数据读取线程已启动...")

        print("正在启动Web服务器...")

        # 等待几秒确保数据开始流动
        time.sleep(2)

        # 打开浏览器
        webbrowser.open('http://127.0.0.1:5000')

        print("地图服务已启动: http://127.0.0.1:5000")
        print("请观察控制台输出，查看GPS数据接收情况")
        print("按 Ctrl+C 停止程序")

        try:
            app.run(host='127.0.0.1', port=5000, debug=False, use_reloader=False)
        except KeyboardInterrupt:
            self.running = False
            if self.ser:
                self.ser.close()
            print("程序已退出")


if __name__ == "__main__":
    tracker = BN808Tracker()
    tracker.start()